今天大概會聊到的範圍
- Snapshot system
上一篇有提到,State 改變時會觸發 recomposition。視這個行為是一種定義。但是為什麼?這怎麼發生的呢?
今天要介紹的是 Compose 中另一個與 State 息息相關的概念 - Snapshot
Snapshot 可以想像是當下所有 State 的存檔、快照。就像遊戲存檔或是電腦備份一樣,當下是什麼就是什麼,全部存起來。
提醒:Snapshot 通常不會用在 Compose 的開發中,主要是在用於 Compose 的內部運作。
讓我們看看 Snapshot 實際上怎麼使用的:
fun main() {
// 1.
val data = mutableStateOf("")
// 2.
data.value = "Foo"
println("2: ${data.value}")
// 3.
val snap = Snapshot.takeSnapshot()
// 4.
data.value = "Bar"
println("4: ${data.value}")
// 5.
snap.enter {
println("5: ${data.value}")
}
// 6.
println("6: ${data.value}")
// 7.
snap.dispose()
}
Snapshot 和 State 的運作,是不一定要在 composable function 中執行的
Output:
2: Foo
4: Bar
5: Foo
6: Bar
mutableStateOf
建立 State。Snapshot.enter
可以 access 當時拍的 snapshot,在這個 scope 內,我們可以取得當時的 statedispose
釋放掉資源Snapshot 的概念就是這麼單純:保留某一個時刻的所有 State。
在 enter snapshot 後,如果我們還想對 State 進行編輯,將會得到錯誤。
snap.enter {
data.value = "Buzz" // << --- Error
}
在 snapshot 中,State 是唯讀無法編輯的。若希望編輯,我們會需要 MutableSnapshot
fun main() {
val data = mutableStateOf("")
data.value = "Foo"
// 1.
val mutableSnap = Snapshot.takeMutableSnapshot()
// 2.
mutableSnap.enter {
println("2: ${data.value}")
data.value = "Buzz"
println("3: ${data.value}")
}
println("4: ${data.value}")
// 5.
mutableSnap.enter {
println("5: ${data.value}")
}
// 6.
mutableSnap.apply()
println("6: ${data.value}")
mutableSnap.dispose()
}
Output:
2: Foo
3: Buzz
4: Foo
5: Buzz
6: Buzz
takeMutableSnapshot
來建立 MutableSnapshot
MutableSnapshot
中,我們一樣可以拿到 takeSnapshot
時的資料 "Foo"MutableSnapshot
中,我們可以對 Sate 進行編輯MutableSnapshot.apply()
我們可以將 snapshot 的值實際賦予到 State 身上takeMutableSnapshot
和 apply
之間,有人直接對 State set value,apply
也不會發生作用fun takeMutableSnapshot(
readObserver: ((Any) -> Unit)? = null,
writeObserver: ((Any) -> Unit)? = null
): MutableSnapshot =
(currentSnapshot() as? MutableSnapshot)?.takeNestedMutableSnapshot(
readObserver,
writeObserver
) ?: error("Cannot create a mutable snapshot of an read-only snapshot")
其實仔細看看 takeMutableSnapshot
的 signature,會發現其實他還有吃兩個參數:read/write 的 Observer。
實際上我們可以透過這兩個角色去監聽到資料被寫入、被讀取的時機,並且取得即將被異動的 State 與其資料。
fun main() {
val data = mutableStateOf("")
data.value = "Foo"
val readObserver = { readState: Any -> if (readState == data) println("READ")}
val writeObserver = { readState: Any -> if (readState == data) println("WRITE")}
val mutableSnap = Snapshot.takeMutableSnapshot(readObserver, writeObserver)
mutableSnap.enter {
println("1: ${data.value}")
data.value = "Buzz"
println("2: ${data.value}")
}
println("3: ${data.value}")
mutableSnap.apply()
println("4: ${data.value}")
mutableSnap.dispose()
}
READ
1: Foo
WRITE
READ
2: Buzz
3: Foo
4: Buzz
在
println
前,我們需要去讀取data.value
並將資料喂給println
function ,因此每次在 snapshot 內的println
之前都會觸發 READ
Composable 最後會被丟到 Recomposer
去執行,在 Recomposer
中我們可以發現這段 code
private inline fun <T> composing(
composition: ControlledComposition,
modifiedValues: IdentityArraySet<Any>?,
block: () -> T
): T {
val snapshot = Snapshot.takeMutableSnapshot(
readObserverOf(composition), writeObserverOf(composition, modifiedValues)
)
try {
return snapshot.enter(block)
} finally {
applyAndCheck(snapshot)
}
}
private fun readObserverOf(composition: ControlledComposition): (Any) -> Unit {
return { value -> composition.recordReadOf(value) }
}
//
override fun recordReadOf(value: Any) {
if (!areChildrenComposing) {
composer.currentRecomposeScope?.let { // <---
it.used = true
observations.add(value, it)
...
}
}
}
Recomposer
在 composing 時,利用 snapshot 的 read/write observer 關注在 snapshot 中的 state 變化。並且在 read observer 時建立 recompose scope。
private fun writeObserverOf(
composition: ControlledComposition,
modifiedValues: IdentityArraySet<Any>?
): (Any) -> Unit {
return { value ->
composition.recordWriteOf(value)
modifiedValues?.add(value)
}
}
override fun recordWriteOf(value: Any) = synchronized(lock) {
// ...
derivedStates.forEachScopeOf(value) { // <---
invalidateScopeOfLocked(it)
}
}
在資料變動時,會 iterate 各個 scope 並且將他們 invalidate。
今天聊到的東西非常的底層,因為篇幅的關係,有些邏輯沒有說明到。非常推薦大家到下面 Reference 的地方閱讀本篇主要參考的文章。看了這些之後感覺對整個 compose 運作流程又再更近一步的認識了!
Reference: